/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.gallery3d.ui;

import com.android.gallery3d.app.AbstractGalleryActivity;
import com.android.gallery3d.data.DataManager;
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.data.Path;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

public class SelectionManager {
	@SuppressWarnings("unused")
	private static final String TAG = SelectionManager.class.getSimpleName();

	public static final int ENTER_SELECTION_MODE = 1;
	public static final int LEAVE_SELECTION_MODE = 2;
	public static final int SELECT_ALL_MODE = 3;

	private Set<Path> mClickedSet;
	private MediaSet mSourceMediaSet;
	private SelectionListener mListener;
	private DataManager mDataManager;
	private boolean mInverseSelection;
	private boolean mIsAlbumSet;
	private boolean mInSelectionMode;
	private boolean mAutoLeave = true;
	private int mTotal;

	public interface SelectionListener {
		public void onSelectionModeChange(int mode);

		public void onSelectionChange(Path path, boolean selected);
	}

	public SelectionManager(AbstractGalleryActivity activity, boolean isAlbumSet) {
		mDataManager = activity.getDataManager();
		mClickedSet = new HashSet<Path>();
		mIsAlbumSet = isAlbumSet;
		mTotal = -1;
	}

	// Whether we will leave selection mode automatically once the number of
	// selected items is down to zero.
	public void setAutoLeaveSelectionMode(boolean enable) {
		mAutoLeave = enable;
	}

	public void setSelectionListener(SelectionListener listener) {
		mListener = listener;
	}

	public void selectAll() {
		mInverseSelection = true;
		mClickedSet.clear();
		mTotal = -1;
		enterSelectionMode();
		if (mListener != null)
			mListener.onSelectionModeChange(SELECT_ALL_MODE);
	}

	public void deSelectAll() {
		leaveSelectionMode();
		mInverseSelection = false;
		mClickedSet.clear();
	}

	public boolean inSelectAllMode() {
		return mInverseSelection;
	}

	public boolean inSelectionMode() {
		return mInSelectionMode;
	}

	public void enterSelectionMode() {
		if (mInSelectionMode)
			return;

		mInSelectionMode = true;
		if (mListener != null)
			mListener.onSelectionModeChange(ENTER_SELECTION_MODE);
	}

	public void leaveSelectionMode() {
		if (!mInSelectionMode)
			return;

		mInSelectionMode = false;
		mInverseSelection = false;
		mClickedSet.clear();
		if (mListener != null)
			mListener.onSelectionModeChange(LEAVE_SELECTION_MODE);
	}

	public boolean isItemSelected(Path itemId) {
		return mInverseSelection ^ mClickedSet.contains(itemId);
	}

	private int getTotalCount() {
		if (mSourceMediaSet == null)
			return -1;

		if (mTotal < 0) {
			mTotal = mIsAlbumSet ? mSourceMediaSet.getSubMediaSetCount()
					: mSourceMediaSet.getMediaItemCount();
		}
		return mTotal;
	}

	public int getSelectedCount() {
		int count = mClickedSet.size();
		if (mInverseSelection) {
			count = getTotalCount() - count;
		}
		return count;
	}

	public void toggle(Path path) {
		if (mClickedSet.contains(path)) {
			mClickedSet.remove(path);
		} else {
			enterSelectionMode();
			mClickedSet.add(path);
		}

		// Convert to inverse selection mode if everything is selected.
		int count = getSelectedCount();
		if (count == getTotalCount()) {
			selectAll();
		}

		if (mListener != null)
			mListener.onSelectionChange(path, isItemSelected(path));
		if (count == 0 && mAutoLeave) {
			leaveSelectionMode();
		}
	}

	private static boolean expandMediaSet(ArrayList<Path> items, MediaSet set,
			int maxSelection) {
		int subCount = set.getSubMediaSetCount();
		for (int i = 0; i < subCount; i++) {
			if (!expandMediaSet(items, set.getSubMediaSet(i), maxSelection)) {
				return false;
			}
		}
		int total = set.getMediaItemCount();
		int batch = 50;
		int index = 0;

		while (index < total) {
			int count = index + batch < total ? batch : total - index;
			ArrayList<MediaItem> list = set.getMediaItem(index, count);
			if (list != null && list.size() > (maxSelection - items.size())) {
				return false;
			}
			for (MediaItem item : list) {
				items.add(item.getPath());
			}
			index += batch;
		}
		return true;
	}

	public ArrayList<Path> getSelected(boolean expandSet) {
		return getSelected(expandSet, Integer.MAX_VALUE);
	}

	public ArrayList<Path> getSelected(boolean expandSet, int maxSelection) {
		ArrayList<Path> selected = new ArrayList<Path>();
		if (mIsAlbumSet) {
			if (mInverseSelection) {
				int total = getTotalCount();
				for (int i = 0; i < total; i++) {
					MediaSet set = mSourceMediaSet.getSubMediaSet(i);
					Path id = set.getPath();
					if (!mClickedSet.contains(id)) {
						if (expandSet) {
							if (!expandMediaSet(selected, set, maxSelection)) {
								return null;
							}
						} else {
							selected.add(id);
							if (selected.size() > maxSelection) {
								return null;
							}
						}
					}
				}
			} else {
				for (Path id : mClickedSet) {
					if (expandSet) {
						if (!expandMediaSet(selected,
								mDataManager.getMediaSet(id), maxSelection)) {
							return null;
						}
					} else {
						selected.add(id);
						if (selected.size() > maxSelection) {
							return null;
						}
					}
				}
			}
		} else {
			if (mInverseSelection) {
				int total = getTotalCount();
				int index = 0;
				while (index < total) {
					int count = Math.min(total - index,
							MediaSet.MEDIAITEM_BATCH_FETCH_COUNT);
					ArrayList<MediaItem> list = mSourceMediaSet.getMediaItem(
							index, count);
					for (MediaItem item : list) {
						Path id = item.getPath();
						if (!mClickedSet.contains(id)) {
							selected.add(id);
							if (selected.size() > maxSelection) {
								return null;
							}
						}
					}
					index += count;
				}
			} else {
				for (Path id : mClickedSet) {
					selected.add(id);
					if (selected.size() > maxSelection) {
						return null;
					}
				}
			}
		}
		return selected;
	}

	public void setSourceMediaSet(MediaSet set) {
		mSourceMediaSet = set;
		mTotal = -1;
	}
}
